#import <stdio.h>
#import <math.h>
#import <strings.h>
#import <stdlib.h>
#import <streams/streams.h>
#import <appkit/graphics.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/color.h>
#import "ppm.h"
#import "NXBitmapImageRepControl.h"
#import "ImageControl.h"

@implementation PPM

	NXStream		*myStream;
	unsigned char		*data[5];
	char				type[5];
	int				width, height, levels;
	int				bits;
	int				cwid;
	int				iwid;
	int				padBits;

- init
{
	iwid = sizeof(int) * 8;
	cwid = sizeof(char) * 8;

	return self;
}

- free
{
	return self;
}

- killToEOL
{
	while (NXGetc(myStream) != '\n') ;
	return self;
}

- getString: (char *)str from: (NXStream *)stream
{
	char		buffer[256];
	
	do {
		NXScanf(stream, "%s", str);
		if (str[0] == '#') {
			NXScanf(stream, "%[^\n]", buffer); (void)NXGetc(stream);
		}
	} while (str[0] == '#');

	return self;
}

- (int)getIntFrom: (NXStream *)stream
{
	char		buffer[256];
	
	do {
		NXScanf(stream, "%s", buffer);
		if (buffer[0] == '#') {
			NXScanf(stream, "%[^\n]", buffer); (void)NXGetc(stream);
		}
		else break;
	} while (1);
	return(atoi(buffer));
}

- (BOOL)readRawFromStream
{
	int				x, y, shift, curInt;
	unsigned int		c[3];
	unsigned int		*dataII[3];
	
	dataII[0] = (unsigned int *)data[0];
	dataII[1] = (unsigned int *)data[1];
	dataII[2] = (unsigned int *)data[2];

#ifdef DEBUG
	fprintf(stderr, "Okay, we're off and reading raw data\n");
#endif

	[self killToEOL];

	shift = 0;
	dataII[0][curInt = 0] = 0;
	dataII[1][0] = 0;
	dataII[2][0] = 0;
	for (x = 0; x <  height; x++) {
		for (y = 0; y < width; y++) {
			if (!shift) {
				dataII[0][curInt] = 0;
				dataII[1][curInt] = 0;
				dataII[2][curInt] = 0;
			}
			c[0] = [self getIntFrom: myStream];
			c[1] = [self getIntFrom: myStream];
			c[2] = [self getIntFrom: myStream];
			c[0] = c[0] << (iwid - bits);
			c[1] = c[1] << (iwid - bits);
			c[2] = c[2] << (iwid - bits);
			if (NXAtEOS(myStream)) return NO;
			if (shift + bits == iwid) {
				dataII[0][curInt] 	|= (c[0] >> shift);
				dataII[1][curInt] 	|= (c[1] >> shift);
				dataII[2][curInt] 	|= (c[2] >> shift);
				curInt++;
				shift = 0;
			}
			else if (shift + bits > iwid) {
				dataII[0][curInt] 		|= (c[0] >> shift);
				dataII[1][curInt] 		|= (c[1] >> shift);
				dataII[2][curInt++] 	|= (c[2] >> shift);
				shift = shift + bits - iwid;
				dataII[0][curInt] = c[0] << (bits - shift);
				dataII[1][curInt] = c[1] << (bits - shift);
				dataII[2][curInt] = c[2] << (bits - shift);
			}
			else {
				dataII[0][curInt] |= (c[0] >> shift);
				dataII[1][curInt] |= (c[1] >> shift);
				dataII[2][curInt] |= (c[2] >> shift);
				shift += bits;
			}
		}
		shift += padBits;
	}

	return YES;
}

- (BOOL)readCompressedFromStream
{
	int				x, y, shift, curByte;
	unsigned char		c[3];

#ifdef DEBUG
	fprintf(stderr, "Okay, we're off and reading condensed data\n");
#endif

	[self killToEOL];

	shift = 0;
	data[0][curByte = 0] = 0;
	data[1][0] = 0;
	data[2][0] = 0;
	for (x = 0; x < height; x++) {
		for (y = 0; y < width; y++) {
			c[0] = NXGetc(myStream) << cwid - bits;
			c[1] = NXGetc(myStream) << cwid - bits;
			c[2] = NXGetc(myStream) << cwid - bits;
			if (NXAtEOS(myStream)) return NO;
			if (shift + bits == cwid) {
				data[0][curByte] 		|= (c[0] >> shift);
				data[1][curByte] 		|= (c[1] >> shift);
				data[2][curByte++] 	|= (c[2] >> shift);
				shift = 0;
				data[0][curByte] = 0;
				data[1][curByte] = 0;
				data[2][curByte] = 0;
			}
			else if (shift + bits > cwid) {
				data[0][curByte] 		|= (c[0] >> shift);
				data[1][curByte] 		|= (c[1] >> shift);
				data[2][curByte++] 	|= (c[2] >> shift);
				shift = shift + bits - cwid;
				data[0][curByte] = c[0] << (bits - shift);
				data[1][curByte] = c[1] << (bits - shift);
				data[2][curByte] = c[2] << (bits - shift);
			}
			else {
				data[0][curByte] |= (c[0] >> shift);
				data[1][curByte] |= (c[1] >> shift);
				data[2][curByte] |= (c[2] >> shift);
				shift += bits;
			}
		}
		shift += padBits;
	}

	return YES;
}

- readFromStream: (NXStream *)stream from: sender;
{
	id		image;

#ifdef DEBUG
	fprintf(stderr, "Made it to read\n");
#endif

	myStream = stream;

	[self getString: type from: myStream];
#ifdef DEBUG
	fprintf(stderr, "Type: %s\n", type);
#endif
	width = [self getIntFrom: myStream];
	height = [self getIntFrom: myStream];
	levels = [self getIntFrom: myStream];
	bits = log(levels + 1) / log (2);
	cwid = sizeof(unsigned char) * 8;
	iwid = sizeof(unsigned int) * 8;
#ifdef DEBUG
	fprintf(stderr, "Type %s of dimensions %dx%d and levels %d or %d bits. intbits = %d, charbits = %d\n", type, width, height, levels + 1, bits, iwid, cwid);
#endif

	image = [[NXBitmapImageRep alloc] 	initDataPlanes: NULL
										pixelsWide: width
										pixelsHigh: height
										bitsPerSample: bits
										samplesPerPixel: 3
										hasAlpha: NO
										isPlanar: YES
										colorSpace: NX_RGBColorSpace
										bytesPerRow: 0
										bitsPerPixel: 0];
	if (!image) return nil;
	[image getDataPlanes: data]; 
#ifdef DEBUG
	fprintf(stderr, "%p %p %p\n", data[0], data[1], data[2]);
#endif

	padBits = [image bytesPerRow] * 8 - bits * width;

	if (!strcmp(type, "P3")) {
		if (![self readRawFromStream]) {
			[image free];
			return nil;
		}
	}
	else if (!strcmp(type, "P6")) {
		if (![self readCompressedFromStream]) {
			[image free];
			return nil;
		}
	}
	else return nil;
	
	return image;
}

static BOOL WriteRaw(id image, NXStream *stream, id imageCon)
{
	int					w = [image pixelsWide],
						h = [image pixelsHigh],
						length = w * h;
	GetPixelNextFunc		myFunc;
	Pixel				*pixel;
	NXColorSpace		cs = [image colorSpace];
	NXColor				tColor;
	float					maxColor = pow(2, (float)[image bitsPerSample]) - 1.0;
	int					x, y;

	NXPrintf(stream, "P3\n");
	NXPrintf(stream, "%d %d\n", w, h);
	NXPrintf(stream, "%d\n", (int)maxColor);
	[imageCon resetNext];
	myFunc = [imageCon getNextFunction];
	for (x = y = 0; x < length; x++, y++) {
		pixel = myFunc();
		if (cs == NX_OneIsBlackColorSpace) {
			NXPrintf(stream, "%d ", 1 - pixel->values[0]);
			NXPrintf(stream, "%d ", 1 - pixel->values[0]);
			NXPrintf(stream, "%d ", 1 - pixel->values[0]);
		} else if (cs == NX_OneIsWhiteColorSpace) {
			NXPrintf(stream, "%d ", pixel->values[0]);
			NXPrintf(stream, "%d ", pixel->values[0]);
			NXPrintf(stream, "%d ", pixel->values[0]);
		} else if (cs == NX_RGBColorSpace) {
			NXPrintf(stream, "%d ", pixel->values[0]);
			NXPrintf(stream, "%d ", pixel->values[1]);
			NXPrintf(stream, "%d ", pixel->values[2]);
		} else if (cs == NX_CMYKColorSpace) {
			tColor = NXConvertCMYKToColor(
								(float)pixel->values[0] / maxColor,
								(float)pixel->values[1] / maxColor,
								(float)pixel->values[2] / maxColor,
								(float)pixel->values[3] / maxColor);
			NXPrintf(stream, "%d ", NXRedComponent(tColor) * maxColor);
			NXPrintf(stream, "%d ", NXGreenComponent(tColor) * maxColor);
			NXPrintf(stream, "%d ", NXBlueComponent(tColor) * maxColor);
		}
		if (y == 6) {
			y = 0;
			NXPrintf(stream, "\n");
		}
	}

	return YES;
}

static BOOL WriteCompact(id image, NXStream *stream, id imageCon)
{
	int					w = [image pixelsWide],
						h = [image pixelsHigh],
						length = w * h;
	GetPixelNextFunc		myFunc;
	Pixel				*pixel;
	NXColorSpace		cs = [image colorSpace];
	NXColor				tColor;
	float					maxColor = pow(2, (float)[image bitsPerSample]) - 1.0;
	int					x;

	NXPrintf(stream, "P6\n");
	NXPrintf(stream, "%d %d\n", w, h);
	NXPrintf(stream, "%d\n", (int)maxColor);
	[imageCon resetNext];
	myFunc = [imageCon getNextFunction];
	for (x = 0; x < length; x++) {
		pixel = myFunc();
		if (cs == NX_OneIsBlackColorSpace) {
			NXPutc(stream, 1 - pixel->values[0]);
			NXPutc(stream, 1 - pixel->values[0]);
			NXPutc(stream, 1 - pixel->values[0]);
		} else if (cs == NX_OneIsWhiteColorSpace) {
			NXPutc(stream, pixel->values[0]);
			NXPutc(stream, pixel->values[0]);
			NXPutc(stream, pixel->values[0]);
		} else if (cs == NX_RGBColorSpace) {
			NXPutc(stream, pixel->values[0]);
			NXPutc(stream, pixel->values[1]);
			NXPutc(stream, pixel->values[2]);
//			fprintf(stderr, "%3d %3d %3d...", pixel->values[0], pixel->values[1], pixel->values[2]);
		} else if (cs == NX_CMYKColorSpace) {
			tColor = NXConvertCMYKToColor(
								(float)pixel->values[0] / maxColor,
								(float)pixel->values[1] / maxColor,
								(float)pixel->values[2] / maxColor,
								(float)pixel->values[3] / maxColor);
			NXPutc(stream, NXRedComponent(tColor) * maxColor);
			NXPutc(stream, NXGreenComponent(tColor) * maxColor);
			NXPutc(stream, NXBlueComponent(tColor) * maxColor);
		}
	}

	return YES;
}

- (BOOL)write: (id)image toStream: (NXStream *)stream from: sender;
{
	id		imageCon = [sender getImageControl: image];

	if ([image bitsPerSample] > 8) {
		return WriteRaw(image, stream, imageCon);
	} else {
		return WriteCompact(image, stream, imageCon);
	}
}

- readAllFromStream: (NXStream *)stream from: sender
{
	return nil;
}

- (BOOL)writeAll: (id)image toStream: (NXStream *)stream
{
	return NO;
}

- customSaveView: (int)width
{
	return nil;
}

- customOpenView: (int)width
{
	return nil;
}

- (char *)getFormatName
{
	return("Portable Pixmap (PPM)");
}

 - (BOOL)setCustomParameter: (const char *)parameter withValue: (void *)ptr
{
	return NO;
}

 - (void *)getCustomParameter: (const char *)parameter
{
	return nil;
}

- (char *)copyrightNotice
{
	return "PPM Converter\nby Alex Raftis\nCopyright (c) 1991 Cal Poly State University\nEmail bugs to alex@data.ACS.CalPoly.EDU";
}
 
- (int)errorState
{
	return CONVERT_ERR_NONE;
}
 
- (int)errorMessage
{
	return ERROR_NO_ERROR;
}

- (char *)errorStringMessage
{
	return NULL;
}
 
- (BOOL)needsWindowServer;
{
	return NO;
}

- (char *)protocolVersion
{
	return "1.0";
}

@end
